# !/usr/bin/env python
# -*- coding: utf-8 -*-

"""
根据传入 cospace 的路径，生成settings.cmake文件
"""
import os.path
import re
from datetime import datetime
from yson.yson_file import YsonFile, ParserType
from lark.lexer import Token

IGNORE_KEYS = ["version", "environments", "environment", "lang_module"]


class CreateSettingsCmakeFile(object):


    def __init__(self, filepath, root_path=None, cmake_filepath=None):
        """
        settings.json 文件路径
        :param filepath: settings.json 文件路径
        :param root_path: cospace 的根路径
        :param cmake_filepath: settings.cmake 文件存储路径。当 cmake_filepath 为 None 时，settings.cmake 文件存储在 root_path/.cache/settings.cmake;
                               当 cmake_filepath 为 None && root_path 为 None 时，settings.cmake 文件存储在当前目录下
        """
        self.filepath = filepath
        self.root_path = root_path
        self.settings: YsonFile | None = None
        self.cmake_content = list()
        self.macro_prefix_pair = dict() # 记录 settings 中的宏前缀对应关系
        self.cmake_filepath = cmake_filepath

        self._pattern = r'\$\{([^}]+)\}'

    def create_cmake_file(self):
        """
        通过 YsonFile 加载文件, 读取对应的节点信息，生成 settings.cmake 文件
        """
        yson = YsonFile()
        yson.set_parser_type(ParserType.lark)        
        try:
            self.settings = yson.load(self.filepath)
        except Exception as e:
            raise Exception(f"yson load file({self.filepath}) error: {e}.")

        # 解析 settings 文件, 展开保存到 self.cmake_content 中
        self._parse_base_attr()
        self._parse_env_attr()
        self._parse_other_attr()
        
        # 将 self.cmake_content 中的变量进行替换
        self._substitution_cmake_content_var()
        print(f"self.cmake_content: {self.cmake_content}")
                
        # 如果设置了 self.root_path, 则将变量中的相对路径转换成绝对路径, 并将路径中的 "\" 替换为 "/"
        self._replace_slash()
        self._build_cmake_file()        
        
    def _expand_token_node(self, node, pre_key) -> list:
        """
        默认 token 是字符串类型
        """
        return [pre_key, node.value]
    
    def _expand_list_node(self, list_node: list, pre_key) -> list:
        """
        默认 list 成员项都是 string 类型的 token
        """
        return [pre_key, [item.value for item in list_node]]
    
    def _expand_dict_node(self, dict_node: dict, pre_key, format_str="{pre_key}.{key}"):
        """
        """
        expand_result = list()
        for key, value in dict_node.items():
            key = key.value
            if isinstance(value, Token):
                expand_result.append(self._expand_token_node(value, format_str.format(pre_key=pre_key, key=key) if pre_key is not None else key))
            elif isinstance(value, (list, tuple)):
                expand_result.append(self._expand_list_node(value, format_str.format(pre_key=pre_key, key=key) if pre_key is not None else key))
            elif isinstance(value, dict):
                expand_result.extend(self._expand_dict_node(value, format_str.format(pre_key=pre_key, key=key) if pre_key is not None else key))
        return expand_result    
    
    def _parse_base_attr(self):
        """
        解析基础属性
        固定的基础属性有：
        WRP_WORK_VERSION： 对应 version
        WRP_SETTINGS_FILE: 文件路径
        """
        if "version" in self.settings:
            self.cmake_content.append(["WRP_WORK_VERSION", self.settings["version"].value])
        else:
            self.cmake_content.append(["WRP_WORK_VERSION", None])
        self.cmake_content.append(["WRP_SETTINGS_FILE", self.filepath])
        
        for key, value in self.settings.items():
            if key in IGNORE_KEYS:
                continue                
            if isinstance(value, Token) and value.type == "STRING":
                self.cmake_content.append([key.value, value.value])
                    
    def _parse_env_attr(self):
        """
        解析环境属性
        """
        temp_settings = None
        if "environment" in self.settings:
            temp_settings = self.settings["environment"]
        elif "environments" in self.settings:
            temp_settings = self.settings["environments"]
        
        if temp_settings:
            # 生成宏前缀对应关系
            self.macro_prefix_pair["env"] = "environment"
            self.cmake_content.extend(self._expand_dict_node(temp_settings, pre_key="", format_str="ENV{pre_key}{{{key}}}"))            
            self.cmake_content.extend(self._expand_dict_node(temp_settings, pre_key="", format_str="{pre_key}{key}"))

    def _parse_other_attr(self):
        """
        解析其他属性
        除了 两个基础属性外，其他属性如果是字典结构
        """        
        for key, value in self.settings.items():
            if key in IGNORE_KEYS:
                continue
            if isinstance(value, dict):
                if "macro_prefix" in value:
                    self.macro_prefix_pair[value["macro_prefix"].value] = key.value 
                self.cmake_content.extend(self._expand_dict_node(value, pre_key=key.value))

    def _build_cmake_file(self):
        """
        创建 cmake 文件
        """
        if self.cmake_filepath is not None:
            cmake_filepath = self.cmake_filepath
        else:
            cmake_filepath = os.path.join(self.root_path, ".cache", "settings.cmake") if self.root_path is not None else "settings.cmake"
        
        cmake_filepath = os.path.normpath(cmake_filepath)
        
        current_umask = os.umask(0)
        try:
            os.makedirs(os.path.dirname(cmake_filepath), exist_ok=True)            
            os.chmod(os.path.dirname(cmake_filepath), 0o777)
        except Exception as err:
            print(f"build_cmake_file||cmake_filepath({cmake_filepath})||err({err})")
        finally:
            os.umask(current_umask)        
        
        with open(cmake_filepath, "w", encoding="utf-8-sig") as f:
            f.write("# auto generated by create_settings_cmake_file.py\n")
            f.write(f"# create time: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n")
            for each in self.cmake_content:
                if isinstance(each[1], list):
                    list_v =  " ".join([f'"{v}"' for v in each[1]])
                    f.write(f'set({each[0]} ' +  list_v + ')\n')
                else:
                    f.write(f'set({each[0]} "{each[1]}")\n')
    
    def _replace_slash(self):
        def _replace(v: str) -> str: 
            """
            替换字符串中的 "\" 为 "/"
            """
            temp_v = v
            if v.startswith(("../", "./", "..\\", ".\\")):
                temp_v = os.path.normpath(os.path.join(self.root_path, v))
            if os.path.isabs(temp_v) or os.path.isdir(temp_v) or os.path.isfile(temp_v):
                temp_v = temp_v.replace("\\", "/")
            return temp_v
        
        if self.root_path is not None:
            for i, each in enumerate(self.cmake_content):
                if isinstance(each[1], str):
                    self.cmake_content[i][1] = _replace(each[1])
                elif isinstance(each[1], list):
                    self.cmake_content[i][1] = [_replace(v) for v in each[1]]
    
    def _get_value_by_key_path(self, node, key_path: str):
        """
        通过 key_path 获取对应的值
        """
        if "." in key_path:
            pre_key = key_path.split(".")[0]
            sub_key = key_path.split(".")[1]
        else:
            pre_key = key_path
            sub_key = None
        
        if pre_key in node:
            if sub_key is None:
                return True, node[pre_key].value
            else:
                return self._get_value_by_key_path(node[pre_key], sub_key)
        else:
            return False, None
                    
    def _substitution_var(self, value: str):
        """
        将展开的 settings.cmake 中的变量进行替换
        """
        if not isinstance(value, str):
            return value
        
        # 判断 var 字符串是否包含变量
        var_list = re.findall(self._pattern, value)
        
        # 如果不包含变量，直接返回
        if len(var_list) == 0:
            return value
        
        temp_value = value
        # 逐个替换变量
        for var in var_list:
            var_prefix_key = var.split(".")[0]
            if var_prefix_key in self.macro_prefix_pair:
                real_prefix_key = self.macro_prefix_pair[var_prefix_key]
                flag, var_value = self._get_value_by_key_path(self.settings, f"{real_prefix_key}.{var.split('.')[1]}")
                
                if flag:
                    temp_value = temp_value.replace(f"${{{var}}}", var_value)

        return temp_value
        
    def _substitution_cmake_content_var(self):
        """
        将 self.cmake_content 中的变量进行替换
        """
        
        for i, each in enumerate(self.cmake_content):
            if isinstance(each[1], list):
                self.cmake_content[i][1] = [self._substitution_var(v) for v in each[1]]
            else:
                self.cmake_content[i][1] = self._substitution_var(each[1])      
